package file

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	mapset "github.com/deckarep/golang-set"
	"github.com/fsnotify/fsnotify"
)

type Watch struct {
	Watch       *fsnotify.Watcher
	hostList    *[]HostObj
	RcvFileList mapset.Set
}

var filterMap = mapset.NewSet("txt", "pdf", "docx", "doc",
	"xlsx", "xml", "yam", "md",
	"jpeg", "jpg", "png", "go")

func NewWatch(hostList *[]HostObj) *Watch {
	w, _ := fsnotify.NewWatcher()
	watch := Watch{
		Watch:       w,
		hostList:    nil,
		RcvFileList: mapset.NewSet(),
	}
	watch.hostList = hostList

	return &watch
}

// mac上的文件删除和重名逻辑与win上不一样
// mac：删除是---->会判断成重命名的逻辑
// win：删除是---->走正常的删除逻辑
// mac：重命名---->先会走创建一个文件的逻辑再去走重命名逻辑
// win：重命名---->先走重命名逻辑再走创建逻辑
func (w *Watch) WatchDir(dir string) {
	//通过Walk来遍历目录下的所有子目录
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		//这里判断是否为目录，只需监控目录即可
		//目录下的文件也在监控范围内，不需要我们一个一个加
		if info == nil {
			fmt.Println("info == nil-----", path)
			return nil
		}
		if info.IsDir() {
			path, err := filepath.Abs(path)
			if err != nil {
				return err
			}
			err = w.Watch.Add(path)
			if err != nil {
				return err
			}
			fmt.Println("监控 : ", path)
		}
		return nil
	})
	if err != nil {
		fmt.Println("监控目录失败！！！", err)
		return
	}
	//启动协程监听文件夹
	go func() {

		for {
			select {
			case ev := <-w.Watch.Events:
				{
					fmt.Println("------------------")
					fmt.Println("name: ", ev.Name)
					fmt.Println("ev.OP: ", ev.Op.String(), " ----: ", ev.Op)

					if ev.Op&fsnotify.Create == fsnotify.Create {
						println("1")

						timer := time.NewTicker(50 * time.Millisecond)
						select {
						case e := <-w.Watch.Events:
							if e.Op&fsnotify.Rename == fsnotify.Rename {
								//这才是真正的重命名逻辑
								fmt.Println("重命名文件 : ", e.Name)
								new_name := GetFileSuffix(ev.Name, "/")
								old_name := GetFileSuffix(e.Name, "/")

								fmt.Println("准备重命名为：", new_name)
								// old_path := dir[2:] + "/" + old_name
								old_path := GetMidDirByAbsPath(dir, ev.Name) + old_name
								//拼接出要重命名的路径
								new_path := GetMidDirByAbsPath(dir, ev.Name) + new_name

								fmt.Println("Send Rename old: ", old_path, "\tto rename: ", new_path)

								//向别的主机发送消息
								if !w.RcvFileList.Contains(ev.Name) {
									for _, host := range *w.hostList {
										SendRenameMsg(host.IP, host.Port, old_path, new_path)
									}
								}
								w.RcvFileList.Remove(ev.Name)

								//移除监控，添加新的
								//重命名后无法获取原有的文件信息，所以不用判断是否为文件夹，直接移除
								w.Watch.Remove(e.Name)
								w.Watch.Add(ev.Name)
								fmt.Println("------------------")
								continue
							}
							if e.Op&fsnotify.Write == fsnotify.Write {
								//在mac上写入一般不会触发，当写入出发的时候前面一般会跟随创建
								//所以这里和外面的写入都一样不需要做逻辑处理
								fmt.Println("创建+写文件----所以是系统更新文件")
							}

						case <-timer.C:
						}

						//这里获取新创建文件的信息，如果是目录，则加入监控中
						fi, err := os.Stat(ev.Name)
						if err == nil && fi.IsDir() {
							w.Watch.Add(ev.Name)
							fmt.Println("添加监控 : ", ev.Name)

						} else {
							fmt.Println("创建文件 : ", ev.Name)
							//过滤一下要发送的文件
							fmt.Println("包含文件？", w.RcvFileList.Contains(ev.Name))
							if !w.RcvFileList.Contains(ev.Name) && FileFilter(GetFileSuffix(ev.Name, "."), filterMap) {
								//向别的主机发送消息
								for _, host := range *w.hostList {
									err := SendFiles(host.IP, host.Port, dir, ev.Name)
									if err != nil {
										fmt.Println("发送文件失败, 可能链接被拒绝!", err)
									}
								}
								// SendFiles("0.0.0.0", "9989", dir, ev.Name)
							}
							w.RcvFileList.Remove(ev.Name)
						}
					}
					if ev.Op&fsnotify.Write == fsnotify.Write {
						//如果不是写入文件夹就发送，主要是为了适配win系统
						// fi, err := os.Stat(ev.Name)
						// if err == nil && !fi.IsDir() {
						// 	println("2")
						// 	fmt.Println("写入文件 : ", ev.Name)
						// 	fmt.Println("包含写文件？", w.RcvFileList.Contains((ev.Name)))
						// 	//过滤一下要发送的文件
						// 	if !w.RcvFileList.Contains(ev.Name) && FileFilter(GetFileSuffix(ev.Name, "."), filterMap) {
						// 		//向别的主机发送消息
						// 		for _, host := range w.hostList.Hosts {
						// 			SendFiles(host.IP, host.Port, dir, ev.Name)
						// 		}
						// 	}
						// 	w.RcvFileList.Remove(ev.Name)
						// }
					}

					if ev.Op&fsnotify.Remove == fsnotify.Remove {
						println("3")

						file_name := GetFileSuffix(ev.Name, "/")

						fmt.Println("准备删除文件：", file_name)
						// p := "./test/sharedSpace/" + dir[2:] + "/" + file_name
						p := GetMidDirByAbsPath(dir, ev.Name) + file_name
						// os.RemoveAll(p)
						fmt.Println("删除文件 : ", p)
						fmt.Println("包含删除文件？", w.RcvFileList.Contains((ev.Name)))
						//向别的主机发送消息
						if !w.RcvFileList.Contains(ev.Name) {
							for _, host := range *w.hostList {
								SendRemoveMsg(host.IP, host.Port, p)
							}
						}
						w.RcvFileList.Remove(ev.Name)

						//如果删除文件是目录，则移除监控
						fi, err := os.Stat(ev.Name)
						if err == nil && fi.IsDir() {
							w.Watch.Remove(ev.Name)

							fmt.Println("删除监控 : ", ev.Name)
						}
					}
					//mac上的删除文件夹和重命名文件夹都走这个逻辑
					//删除文件只会走一次重命名逻辑
					//重命名会先创建再走重命名逻辑
					if ev.Op&fsnotify.Rename == fsnotify.Rename {
						println("4")
						// timer := time.NewTicker(50 * time.Millisecond)
						// select{
						// case e := <-w.Watch.Events:
						// 	fmt.Println("移动文件")
						// case <-timer.C:
						// }

						fmt.Println("重命名win/删除mac 文件 : ", ev.Name)
						file_name := GetFileSuffix(ev.Name, "/")

						fmt.Println("准备删除文件：", file_name)
						// p := dir[2:] + "/" + file_name
						p := GetMidDirByAbsPath(dir, ev.Name) + file_name

						fmt.Println("删除文件 : ", p)
						//向别的主机发送消息
						if !w.RcvFileList.Contains(ev.Name) {
							for _, host := range *w.hostList {
								SendRemoveMsg(host.IP, host.Port, p)
							}
						}
						w.RcvFileList.Remove(ev.Name)

						// SendRemoveMsg("0.0.0.0", "9989", p)
						//如果删除文件是目录，则移除监控
						//mac上的删除是这个，所以要加一个判断
						fi, err := os.Stat(ev.Name)
						if err == nil && fi.IsDir() {
							w.Watch.Remove(ev.Name)
							fmt.Println("删除监控 : ", ev.Name)
						}
					}
					fmt.Println("------------------")

				}
			case err := <-w.Watch.Errors:
				{
					fmt.Println("error : ", err)
					return
				}
			}
		}
	}()
}
